{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# `tvm::runtime::Object`\n",
    "\n",
    "参考：`tvm/include/tvm/runtime/object.h`\n",
    "\n",
    "## `TypeIndex`\n",
    "\n",
    "```{admonition} 源码中为什么要将 enum 类型放在 struct 结构体中呢？\n",
    "参考：[C++ enum 命名冲突问题](https://zhuanlan.zhihu.com/p/373998828)\n",
    "\n",
    "包含在 `struct` 的 `{}` 中，相当于在一个命名空间下，这样已经能够避免命名冲突的问题，而且 `TypeIndex::ENumName` 使用起来比较方便。\n",
    "```\n",
    "\n",
    "`TypeIndex` 用于 {class}`tvm::runtime::Object` 的成员变量 `_type_index`。\n",
    "\n",
    "使用 Python 模拟 `TypeIndex`（共 4 类）:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from enum import Enum\n",
    "\n",
    "class TypeIndex(Enum):\n",
    "    kRoot: int = 0 # 1. root object 类型\n",
    "    # 2. 标准的静态索引赋值，前端可以利用这些常量。\n",
    "    kRuntimeModule: int = 1 # runtime::Module\n",
    "    kRuntimeNDArray: int = 2 # runtime::NDArray\n",
    "    kRuntimeString: int = 3 # runtime::String\n",
    "    kRuntimeArray: int = 4 # runtime::Array\n",
    "    kRuntimeMap: int = 5 # runtime::Map\n",
    "    kRuntimeShapeTuple: int = 6 # runtime::ShapeTuple\n",
    "    kRuntimePackedFunc: int = 7 # runtime::PackedFunc\n",
    "    # 3. 可能需要更改的静态赋值。\n",
    "    kRuntimeClosure: int = 8\n",
    "    kRuntimeADT: int = 9\n",
    "    kStaticIndexEnd: int = 10\n",
    "    kDynamic = kStaticIndexEnd # 4. 类型索引在运行时分配。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## `Object`\n",
    "\n",
    "{class}`tvm::runtime::Object` 是 TVM 对象容器的基类。\n",
    "\n",
    "子类应声明以下静态常量字段：\n",
    "\n",
    "- `_type_index`: 对象的静态类型索引，如果分配给 `TypeIndex::kDynamic`，则在运行时分配类型索引。可以通过 `ObjectType::TypeIndex()` 访问运行时类型索引。\n",
    "- `_type_key`: 类型的唯一字符串标识符。\n",
    "- `_type_final`: 该类型是否为终端类型（对象系统中没有该类型的子类）。此字段由宏 `TVM_DECLARE_FINAL_OBJECT_INFO` 自动设置。仍然可以对终端对象类型 `T` 进行子类化，并使用 `make_object` 构造它。但是 `IsInstance` 检查只会显示对象类型是 `T` (而不是子类)。\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{rubric} 如何定义 tvm::runtime::Object 子类？\n",
    "```\n",
    "\n",
    "需要包含两个字段：`_type_child_slots` 和 `_type_child_slots_can_overflow`。\n",
    "\n",
    "- `_type_child_slots`：表示为当前对象类型预留的子类类型索引槽位数，用于运行时优化 `IsInstance` 中的类型检查。如果对象的类型索引在 `[type_index, type_index + _type_child_slots]` 范围内，则可以快速判断该对象是否为当前对象类型的子类。否则，将使用回退机制来检查全局类型表。建议将其设置为估计所需的子类数量。\n",
    "- `_type_child_slots_can_overflow`：表示是否可以在子类数量超过 `_type_child_slots` 的情况下添加额外的子类。如果为 `true`，则会使用回退机制来检查全局类型表。建议将其设置为 `false`，以获得最优的运行时速度(如果我们知道确切的子类数量)。\n",
    "\n",
    "此外，还介绍了两个宏：`TVM_DECLARE_BASE_OBJECT_INFO` 和 `TVM_DECLARE_FINAL_OBJECT_INFO`，用于声明可以被子类化的对象和不可被子类化的对象的辅助函数。\n",
    "\n",
    "也可以使用：\n",
    "\n",
    "- `make_object`：用于创建具有给定 type_index 和 deleter 的新对象的函数。它用于创建动态类型的对象，这些对象可以被其他对象子类化。\n",
    "- `ObjectPtr`：表示指向对象的指针的类。它提供了管理由指针指向的对象生命周期的方法。\n",
    "- `ObjectRef`：表示引用对象的类。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "示例：\n",
    "\n",
    "```c++\n",
    "class BaseObj :public Object {\n",
    "public:\n",
    "    // 对象字段\n",
    "    int field0;\n",
    "\n",
    "    // 对象属性\n",
    "    static constexpr const uint32_t _type_index = TypeIndex::kDynamic;\n",
    "    static constexpr const char* _type_key = \"test.BaseObj\";\n",
    "    // 告诉 TVM 编译器，BaseObj 类是 Object 类的子类，并且需要在编译时进行一些特殊的处理。\n",
    "    TVM_DECLARE_BASE_OBJECT_INFO(BaseObj, Object);\n",
    "};\n",
    "\n",
    "class LeafObj :public BaseObj {\n",
    "public:\n",
    "    // 字段\n",
    "    int child_field0;\n",
    "    // 对象属性\n",
    "    static constexpr const uint32_t _type_index = TypeIndex::kDynamic;\n",
    "    static constexpr const char* _type_key = \"test.LeafObj\";\n",
    "    TVM_DECLARE_BASE_OBJECT_INFO(LeafObj, Object);\n",
    "};\n",
    "```\n",
    "\n",
    "还需要注册：\n",
    "\n",
    "```c++\n",
    "TVM_REGISTER_OBJECT_TYPE(BaseObj);\n",
    "TVM_REGISTER_OBJECT_TYPE(LeafObj);\n",
    "```\n",
    "\n",
    "接下来，便可使用：\n",
    "\n",
    "```c++\n",
    "void TestObjects() {\n",
    "    // 创建对象\n",
    "    ObjectRef leaf_ref(make_object<LeafObj>());\n",
    "    // 转换为特定实例\n",
    "    const LeafObj* leaf_ptr = leaf_ref.as<LeafObj>();\n",
    "    ICHECK(leaf_ptr != nullptr);\n",
    "    // 也可以转换为基类\n",
    "    ICHECK(leaf_ref.as<BaseObj>() != nullptr);\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```{tip}\n",
    "TVM 里有个不成文的约定，所有以 `Node` 为结尾的类名都是继承自 `Object`，不以 `Node` 结尾的类名都是继承自 `ObjectRef`。\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 对象系统常用宏\n",
    "\n",
    "- `TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)` 是辅助宏，用于声明可以被继承的基础对象类型。它接受两个参数 `TypeName` 和 `ParentType`，分别表示当前类型的名称和父类型的名称。\n",
    "    在宏内部，首先使用 `static_assert` 进行编译时断言，确保父类型没有被标记为 final（即不可继承）。然后定义了名为 `RuntimeTypeIndex()` 的静态函数，用于获取对象的运行时类型索引。在 `RuntimeTypeIndex()` 函数内部，再次使用 `static_assert` 进行编译时断言，确保当父类型指定了子类型插槽数时，当前类型也指定了相应的子类型插槽数。然后通过判断当前类型的索引是否为动态类型来确定是否需要调用 `_GetOrAllocRuntimeTypeIndex()` 函数来获取或分配运行时类型索引。`_GetOrAllocRuntimeTypeIndex()` 函数内部使用了 `Object::GetOrAllocRuntimeTypeIndex()` 函数来获取或分配运行时类型索引，并返回该索引。\n",
    "\n",
    "- `TVM_DECLARE_FINAL_OBJECT_INFO(TypeName, ParentType)` 是辅助宏，用于在最终类中声明类型信息。它接受两个参数 `TypeName` 和 `ParentType`，分别表示当前类型的名称和父类型的名称。\n",
    "    在宏内部，首先使用 `static const constexpr` 定义了两个静态常量变量 `_type_final` 和 `_type_child_slots`，分别表示当前类型是否为最终类型和子类型插槽数。然后，调用`TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)`宏来声明基础对象类型信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `TVM_ATTRIBUTE_UNUSED` 是宏定义，用于消除未使用变量或函数的警告。根据编译器的不同，宏的定义方式也不同。在支持 GCC 编译器的平台上，使用 `__attribute__((unused))` 来定义该宏；在其他平台上，则直接定义为空。\n",
    "- `TVM_STR_CONCAT_(__x, __y)` 和 `TVM_STR_CONCAT(__x, __y)` 是两个字符串连接的宏定义。它们的作用是将两个字符串连接起来，生成新的字符串。\n",
    "- `TVM_OBJECT_REG_VAR_DEF` 是宏定义，用于定义静态的、未使用的变量。它使用了 `TVM_ATTRIBUTE_UNUSED` 宏来消除未使用变量的警告。这个变量的类型为 `uint32_t`，名称为 `__make_Object_tid`。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `TVM_REGISTER_OBJECT_TYPE(TypeName)` 是辅助宏，用于将对象类型注册到运行时。它接受参数 `TypeName`，表示要注册的对象类型的名称。在宏内部，使用 `TVM_STR_CONCAT` 宏将 `TVM_OBJECT_REG_VAR_DEF` 和 `__COUNTER__` 连接起来，生成新的字符串。然后，将该字符串赋值为 `TypeName::_GetOrAllocRuntimeTypeIndex()` 的返回值，即该对象的运行时类型索引。这个宏的作用是确保每个终端类都被正确地注册到运行时类型表中。\n",
    "- `TVM_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName)` 是辅助宏，用于定义默认的拷贝/移动构造函数和赋值运算符。它接受参数 `TypeName`，表示要定义构造函数和赋值运算符的类的名称。在宏内部，使用 `TypeName(const TypeName& other) = default;`、`TypeName(TypeName&& other) = default;`、`TypeName& operator=(const TypeName& other) = default;` 和 `TypeName& operator=(TypeName&& other) = default;` 语句分别定义了拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符的默认实现。\n",
    "- `TVM_DEFINE_OBJECT_REF_METHODS(TypeName, ParentType, ObjectName)` 是辅助宏，用于定义对象引用的方法。它接受三个参数 `TypeName`、`ParentType` 和 `ObjectName`，分别表示对象类型名称、父类型名称和对象名称。在宏内部，首先使用 `TypeName() = default;` 语句定义了默认构造函数。然后，使用 `explicit TypeName(::tvm::runtime::ObjectPtr<::tvm::runtime::Object> n) : ParentType(n) {}` 语句定义了带有 `::tvm::runtime::ObjectPtr<::tvm::runtime::Object>` 参数的构造函数，并将传入的参数赋值给 `ParentType` 成员变量。接着，使用 `TVM_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName);` 语句重新定义了默认的拷贝/移动构造函数和赋值运算符。最后，使用 `const ObjectName* operator->() const { return static_cast<const ObjectName*>(data_.get()); }` 和 `const ObjectName* get() const { return operator->(); }` 语句定义了对象引用的方法，包括箭头运算符重载和 `operator->()` 方法。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "tvmz",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
